MechWarrior 4 Mission ABL Tutorial

The following is a walk trough of the MechWarrior 4 Vengeance mission ABL script of Urban 3, Destroy Base, where the player must destroy the turret generator, communication station, and defeat the enemy guarding the local University.  The Tutorial is broken up into actual ABL script; denoted by the bolded font, and the definitions and explanations of the ABL script, denoted with the italicized text.

ABL is an FSM, which stands for Finite State Machine.  This means that the code in any script is organized into states.  Each time the game goes through a script, it only goes through the current, active state.

For example, the script below, things are going to happen in the following order for this script...

1.   The system will define the constant, types and variables.

2.   Values will be set for the variables in the init function.

3.   The system will go to the Startstate.  ALL scripts must have a startstate as the system always goes there immediately after all the constants and variables are defined.

4.  The last command in the start state is "trans opening;".  This means that the start state will be gone through ONCE then the next time the mission script is run the system will go to the opening state.

5.  The opening state is designed to run the opening cinema.  Once the opening cinema is done the system will reach a line reading "trans sit;".  This will tell the system to start running the sit state.

6. The sit state is where all the objective checking occurs.  If the player loses the mission, the script will tell the system to go to the lost state (where the losing cinema sits).  If the player wins the mission then the script will go first to the waitforcinema state (which simply waits 5 seconds while doing nothing) and from there to the "won" state (which is the victory cinema).

By using different states the code can therefore be compartmentalized so that things run cleaner.

The first line of every mission script must read "fsm XXXXX : integer;” XXXXX is the name the system uses when referring to the script and it MUST be unique amongst all the scripts assigned to that mission.

fsm TUTORIAL: integer;		

//------------------------------------------------------------------
//
//     Constant Definitions
//
//------------------------------------------------------------------

The below line tells the system you are about to define constants.

     const

The below line tells the system where the file holding constants is.  This is a standard line, although the file that it points to has been integrated into the release version of MechWarrior4 code.

		#include_ <content\ABLScripts\mwconst.abi>

//------------------------------------------------------------------
//
//     Type Definitions
//
//------------------------------------------------------------------

The below line tells the system that types are about to be defined

     type

The location of the file that lists different types.  This is a standard line, although the file that it points to has been integrated into the release version of MechWarrior4 code.

		#include_ <content\ABLScripts\mwtype.abi>

//------------------------------------------------------------------
//
//     Variable Declarations
//
//------------------------------------------------------------------

The below line tells the system you are about to define variables

      var

An eternal variable is shared amongst all scripts in that mission. The below example shows that a variable is being created called "playermech".  It's an integer, which means that it cannot hold fractions.  It's eternal which means that all the scripts (not only the mission script but also all the unit brains) can read it.  If it was static that would mean only the script where it was defined could read it but it's value is saved throughout the course of the mission if there was no modifier in front of the type of variable that would mean it is a "normal" variable.  A "normal" variable is defined only for that script and is not saved.  Insted it resets to 0 every time the script is run (which means every frame for mission scripts and about every 4 frames for a unit brain.

Note: Eternal variables can ONLY be defined in the mission script.  Defining an eternal variable in a unit brain can cause the game to crash. This variable will eventually be set to the objectid for the 'Mech the player is driving.  It is eternal which means the unit brains for this mission can check it's value.

		eternal integer playermech;
		
An integer used to control the different shots of the opening and closing cinema. 	

		static integer	cinema_cut;

		eternal boolean	generatordead;
		static boolean		commcenterdead;
		static boolean		defendersdead;
		static boolean		commcenterwarningplayed;
		eternal integer	numturretsattacking;
		static boolean		turretsoundplayed;
		
Navpoint Check Variables

		static integer	checkrange;
		static boolean	alphareached;
		static boolean	betareached;
		static boolean	gammareached;
		
//------------------------------------------------------------------
//
//     Init Function    (automatically run first time through)
//
//------------------------------------------------------------------

The initialization function takes all the variables defined above and assigns values to them. This function is called only once at the beginning.  After that it is never checked again.

function init;

You can define a variable inside a state.  Doing so means that the variable had better be used only in that state. 

	var

	code

 Setting starting values for the variables defined above

 		checkrange = 150;
		alphareached = false;
		betareached = false;
		gammareached = false;
 		cinema_cut = 0; 						
		generatordead = false;
		commcenterdead = false;
		defendersdead = false;
		commcenterwarningplayed = false;
		numturretsattacking = 0;
		turretsoundplayed = false;

The below line gets the objectid for whatever unit the player is driving and assigning the value to playermech so playermech can be used throughout both the mission script and the unit brains.

		 playermech = getplayervehicle(epl_player0);	
 	
Adding the player to group 1.  This is essential to any single player mission where the player will have lancemates
			GroupAddObject(GroupObjectId(1),playermech);

The below line makes groups so that things can be checked more easily during the mission
	
			GroupAddObject(GroupObjectId(10),eve_generatorpatrol1);
			GroupAddObject(GroupObjectId(10),eve_generatorpatrol2);
			GroupAddObject(GroupObjectId(10),eve_generatorsentry4);

The below line sets the group ai to radiosquad for this group.  Doing so means that if one unit in the group is shot then all of them return true for isshot.

			SetGroupAI(groupobjectid(10),GROUPAI_RADIOSQUAD);
			 		
			GroupAddObject(GroupObjectId(12),eve_unipatrol1);
			GroupAddObject(GroupObjectId(12),eve_unipatrol2);
			GroupAddObject(GroupObjectId(12),eve_unipatrol3);
			GroupAddObject(GroupObjectId(12),eve_unipatrol4);
			SetGroupAI(groupobjectid(12),GROUPAI_RADIOSQUAD);

			GroupAddObject(GroupObjectId(13),eve_innerpatrol1);
			GroupAddObject(GroupObjectId(13),eve_innerpatrol4);
			SetGroupAI(groupobjectid(13),GROUPAI_RADIOSQUAD);

			GroupAddObject(GroupObjectId(14),eve_unisentry1);
			GroupAddObject(GroupObjectId(14),eve_unisentry2);
			GroupAddObject(GroupObjectId(14),eve_unisentry4);
			SetGroupAI(groupobjectid(14),GROUPAI_RADIOSQUAD);

The below group has no groupai set to it as it is being used only to make objective determination easier. If the Communications center is destroyed, all enemy units become separate AI’s, and are unaware of each other’s state, so this group contains all the units defending the university after the communications center is destroyed. Note that the same unit can be in more than one group.

			GroupAddObject(GroupObjectId(21),eve_unipatrol1);
			GroupAddObject(GroupObjectId(21),eve_unipatrol2);
			GroupAddObject(GroupObjectId(21),eve_unipatrol3);
			GroupAddObject(GroupObjectId(21),eve_unipatrol4);
			GroupAddObject(GroupObjectId(21),eve_innerpatrol1);
			GroupAddObject(GroupObjectId(21),eve_innerpatrol4);
			GroupAddObject(GroupObjectId(21),eve_unisentry1);
			GroupAddObject(GroupObjectId(21),eve_unisentry2);
			GroupAddObject(GroupObjectId(21),eve_unisentry4);

	
endfunction;

//------------------------------------------------------------------
//
//     Main Code
//
//------------------------------------------------------------------

The startstate is the state that the script always begins in.  ALL scripts MUST have a startstate. The startstate is normally used to play the initial voiceovers, run some commands that should be set up only once and then things are transed to a different state and the start state is never checked again.

A line like this must be at the beginning of every state.  It consists of the word "state", the name of the state and a semicolon.  The state name must be unique within the script.

state startState;

	code

The below 3 lines reveal all three nav points and makes nav gamma appear in the player's hud as the active navpoint at the start of the mission.

			revealnavpoint(ena_nav_alpha);
			revealnavpoint(ena_nav_beta);
			setnavpoint(ena_nav_gamma);
			
Reveal all those objectives the player knows about at the beginning of the mission.  The three objectives are:  (1) destroy the turret power generator, (2) Destroy the communication center and (3) destroy the units defending the university.  Only the last of these three objectives MUST be accomplished for the mission to be successful.  The other two are secondary.

			revealobjective(eob_DestroyTurretPowerGenerator);
			revealobjective(eob_DestroyCommCenter);
			revealobjective(eob_DestroyUniversityUnits);
			
Play the intro voiceovers.  Note that the sound system can handle having a large number of sounds sitting in a queue.  This makes it much easier to build conversations.

			playvoiceover("VO\OP6\MI3\intro",talker_Ian);
			playvoiceover("VO\OP6\MI3\intro",talker_Rat);
			playvoiceover("VO\OP6\MI3\intro2",talker_Ian);
			playvoiceover("VO\OP6\MI3\intro2",talker_Rat);
			playvoiceover("VO\OP6\MI3\intro3",talker_Ian);
			playvoiceover("VO\OP6\MI3\intro3",talker_Rat);
			playlancematesound("VO\OP6\MI3\intro4",1);
			playlancematesound("VO\OP6\MI3\intro5",1);

Take away control of the player's 'Mech from the player and give it to the player.able scipt

			Playerai(epl_player0,true);

Start playing some music

			StartMusic("Music\Sander",3.5,3.0,50);

Trans to the opening state.  This is the state where the opening cinema is put together.

			trans opening;

This tells the system that the current state is ending.

endstate;

//------------------------------------------------------------------------------
//
//      Dead State
//
//------------------------------------------------------------------------------

Just like all scripts must have a startstate they also must have a deadstate.  This is the state that a unit brain automatically goes to if the unit is killed.  While there is no way to kill a mission the system demands that a deadstate exists in the mission script as well.

state deadState;
	code
endstate;
	
//------------------------------------------------------------------
//
//     Sit state : description
//
//------------------------------------------------------------------

Defining that the sit state is now beginning.

state sit;

	code

Play the generic startup sounds for the player
	
				PlayVoiceOverOnce("VO\Generic\Startup1",Talker_Bet);
				PlayVoiceOverOnce("VO\Generic\Startup2",Talker_Bet);
				PlayVoiceOverOnce("VO\Generic\Startup3",Talker_Bet);
				PlayVoiceOverOnce("VO\Generic\Startup6",Talker_Bet);

If we have not played the sound about there being a lot of turrets…

	if (not turretsoundplayed) then

…and the number of turrets attacking the player's units is larger than 7 (note that the numturretsattacking variable is eternal and is being changed inside the unit brains for the turrets in this mission)…

		if (numturretsattacking >	7) then

…then play the lancemate sound telling the player that he is being hit by a bunch of turrets.

			playlancematesound("VO\OP6\MI3\turrets",1);

Set the wraparound boolean to true so that the playlancematesound command is only executed once.

			turretsoundplayed = true;

Every If statement MUST have an endif associated with it.

		endif;
	endif;

If the player has been killed then

  	if (isdead(playermech)) then

End the mission as a failure and end it in 12 seconds

		endmission(false,12);

Take control of the player's 'Mech from him and hand it to player.abl

		Playerai(epl_player0,true);

Kill all voiceovers currently playing

		killvoiceovers;

Play a voiceover where Rathburne exclaims something about the player's death.

		PlayVoiceOver("VO\Generic\Iandestroyed",talker_rat);

Fade out whatever music is playing over a 3.5 second period and fade in the defeat music over a 3.0 second period.  Play the new music at 50% volume

		StartMusic("Music\defeat",3.5,3.0,50);

Go to the Lost state.  This is the state where the closing cinema for losing the mission will be played

		trans lost;
	endif;

The following is a boolean variable that is set to false at the beginning of the mission, so long as Commcenterdead is set to false the code bracketed by the below if statement and it's corresponding endif statement will be run.  Once this variable is set to "true", all the code bracketed by the if and endif will no longer be checked.

if (not Commcenterdead) then

If the warning sound telling the player he's near the commcenter has not yet been played then

	if (not commcenterwarningplayed) then

If the player gets within 800 meters of the commcenter then

		if (iswithin(playermech,ebu_Communications_Center,800)) then

Play a sound telling the player he's near the commcenter

			playlancematesound("VO\OP6\MI3\commclose",1);

Set the boolean that is checked above so that this block of code is run only once.

			commcenterwarningplayed = true;

 An endif for every if.

		endif;
	endif;

If the communications center has been destroyed

	if (isdead(ebu_Communications_Center)) then

Make the destroy communications center objective successful

		successobjective(eob_destroycommcenter);

Turn off the groupai for all the groups defending the university.  The university units can no longer talk to each other and share data

		SetGroupAI(groupobjectid(10),GROUPAI_NONE);
		SetGroupAI(groupobjectid(12),GROUPAI_NONE);
		SetGroupAI(groupobjectid(13),GROUPAI_NONE);
		SetGroupAI(groupobjectid(14),GROUPAI_NONE);

Play the "yeah, you did it" sounds!

		playvoiceover("VO\OP6\MI3\commdown",talker_Rat);
		playlancematesound("VO\OP6\MI3\commdown",1);

Set commcenterdead to true so that all the code involving the comm center is not gone through again.

		Commcenterdead = true;
	endif;
endif;

This is another wrapped boolean.  This one will be set to true if the turret power generator is destroyed.

if (not generatordead) then

If the turret power generator has been destroyed then

	if (isdead(ebu_turretpowergenerator)) then

Mark the "destroy turret generators" objective successful.

		successobjective(eob_DestroyTurretPowerGenerator);

Set the generator dead boolean to true so this code is not run through again.

		generatordead = true;

Play a "Yea!  we did it" sound

		playlancematesound("VO\OP6\MI3\gendead",2);
	endif;
endif;

If the units defending the university are ALL dead then…

if (groupalldead((groupobjectid(21)))) then

…end the mission in 30 seconds and tell the player he won

	endmission(true,30);

Mark the "destroy university units" objective successful

	successobjective(eob_DestroyUniversityUnits);

Play some voiceovers

	playlancematesound("VO\OP6\MI3\win",1);
	playvoiceover("VO\OP6\MI3\win",talker_Ian);
	playvoiceover("VO\OP6\MI3\win",talker_Rat);
	playvoiceover("VO\OP6\MI3\tired",talker_Ian);
	playvoiceover("VO\OP6\MI3\tired",talker_Rat);

Take control of the player's 'Mech away and give it to player.abl

	Playerai(epl_player0,true);

Start the victory music, fade out whatever music is playing over a 3.5 second period and fade in the defeat music over a 3.0 second period.  Play the new music at 50% volume

	StartMusic("Music\victory",3.5,3.0,50);

Make everything invulnerable (so that the player is not killed during the success cinema)

	enableglobalinvulnerable;

Make it so that no one wants to target the player (so that he is not getting shot during the victory cinema.

	settargetdesirability(playermech,-1);

Reset and start timer number 28 (this timer will be used to time shots in the victory cinema)

	resettimer(gti_timer_28);
	starttimer(gti_timer_28);

Go to the waitforcinema state

	trans waitforcinema;

endif;		
	
If there is no music playing then

if (not ismusicplaying) then

The below lines set Music to play during the mission.

	StartMusic("Music\urbanambloop",0.0,3.0,40);
endif;

//*************************************
//Navpoint Checks
//*************************************

If the player has not reached Navpoint Alpha yet then

if (not alphareached) then

If nav alpha has been revealed to the player then

	if (isnavrevealed(ena_nav_alpha)) then

If the player is within "checkrange" meters of nav alpha (checkrange is set to 150 meters in the init function).

		if (iswithin(playermech,ena_nav_alpha,checkrange)) then

Play a voiceover telling the player he made it to the Nav

			playvoiceover("VO\generic\navalpha",talker_Bet);

Set the alphareached boolean to true.

			alphareached = true;
		endif;
	endif;
endif;

if (not betareached) then
	if (isnavrevealed(ena_nav_beta)) then
		if (iswithin(playermech,ena_nav_beta,checkrange)) then
			playvoiceover("VO\generic\navbeta",talker_Bet);
			betareached = true;
		endif;
	endif;
endif;

if (not gammareached) then
	if (isnavrevealed(ena_nav_gamma)) then
		if (iswithin(playermech,ena_nav_gamma,checkrange)) then
			playvoiceover("VO\generic\navgamma",talker_Bet);
			gammareached = true;
		endif;
	endif;
endif;

endstate;

//-----------------------------------------
//HERE BE CINEMAS
//-----------------------------------------

The state where the opening cinema is run.

state opening;

	code

The switch checks the value of the variable cinema_cut and runs different code depending on it's value.

switch (cinema_cut)

If cinema_cut is equal to 0 then
			
			case 0:

Start the cinema system

			cinemastart;

Set the active camera

			setactivecamera (eca_cameraship);

Cut to black (fade to black over 0 seconds)

			Fadetoblack(0.0);

Point the camera at the player's 'Mech at all times.

			Targetfollowobject(playermech);

While we're at it, have the camera physically FOLLOW the player's 'Mech as well

			Camerafollowobject(playermech);

Have the camera shake along with the player's feet hitting the ground. The number at the end is the distance from the unit at which a “normal” amount of foot shaking will occur.

			SetCameraFootShake(playermech,100);

Camera offset settings for a object dictate where the camera is located in the world Camera location numerical settings are (X, Y, Z, time, follow terrain)
Over a 0 second period have the camera aim itself about 7 meters up the 'Mechs body (rather than at it's feet, which is the default get).

			targetoffset(0.0,7.0,0.0,0.0,false);

Instantly move the camera itself so that it's 49 meters behind, 49 meters above and 4 meters off to the left of the player's 'Mech.  Note that the camera is STILL pointing at the player's 'Mech since the target is sitting in the middle of the 'Mech about 7 meters up.

			Cameraoffset(-40.0,49.0,4.0,0.0,false);

Now move the camera another 50 meters behind the 'Mech while bringing it down to 12 meters in height and sliding it 8 meters over.  Do this over a 30 second period so it's a nice, smooth camera move.

			Cameraoffset(-90.0,35.0,12.0,30.0,false);

Set a timer with an id number of 21 to 0 seconds and begin it counting up.

			startTimer (gti_Timer_21);
			resettimer (gti_Timer_21);

Add 1 to the cinema_cut variable.  Since it started at 0 (see the case statement above) then cinema_cut is now equal to 1

			cinema_cut = cinema_cut + 1;

Endcase, this is much like endif for an if statement.  Each case must have an endcase associated with it.
			endcase;

If Cinema_cut is equal to 1 then.

			case 1:

If timer 21 has been running for more than one second, then…	
			
				if (timegreater(gti_Timer_21,1)) then

Fade up from black over a 3 second period.	
				
					Fadefromblack(3.0);

Drop timer 21 back down to 0 (it's still running, it's just had it's value reset)

					resetTimer (gti_Timer_21);

Add 1 to cinema_cut.  It's value is now 2

					cinema_cut = cinema_cut + 1;	
				endif;
				
			endcase;

If Cinema_cut is equal to 2 then
							
			case 2:
				
If timer 21 has been running for more than 22 seconds then…

				if (timegreater(gti_Timer_21,22)) then

Set cinema cut back to 0 so it can be used by another cinema

					cinema_cut = 0;

Bring timer 20 back down to 0 and stop it from counting

					resettimer (gti_Timer_21);
					killtimer(gti_timer_21);

Give the player back control of his 'Mech

					playerai(epl_player0,false);

Turn the cinema system off

					cinemaend;

Go to the sit state where we will be checking the objectives

					trans sit;	
				endif;
				
			endcase;

 Just like endif and endcase, every switch must have an endswitch. 
		
		endswitch;

 If the player presses the space bar while the cinema is running then
	
	if (cinemaskip) then

Make cinema_cut = 0 so that it can be used again for the next cinema

			cinema_cut = 0;

Bring timer 21 back down to 0 and stop it from counting

			resettimer (gti_Timer_21);
			killtimer(gti_timer_21);

Give the player back control of his 'Mech

			playerai(epl_player0,false);

Fade up from black instantly in case the player hit space while the screen was black.

      Fadefromblack(0.0);

Turn off any voiceovers that are going on

			KillVoiceOvers;

Turn the cinema system off

		  cinemaend;

Go to the sit state where we will be checking the objectives

		  trans sit;
	endif;

And here is the end of the opening state
	
endstate;

Here is where, if the player wins the mission, the system waits for four seconds before taking control of the player's 'Mech away from him and goes to the won state.

state waitforcinema;
		
		code
		
		if (timegreater(gti_timer_28,4)) then
			playerai(epl_player0,true);
			trans won;
		endif;

endstate;

state won;

		code
		
	if (cinemaskip) then
			cinema_cut = 20;
			resettimer (gti_Timer_21);
			killtimer(gti_timer_21);
			endmission(true,1);	
			trans hang;
	endif;
		
		switch cinema_cut
		
			case 0:	
				CinemaStart;
				setactivecamera (eca_cameraship);
				Targetfollowobject(playermech);
				Camerafollowobject(playermech);
				Targetoffset(0.0,4.0,0.0,0.0,false);
				Cameraoffset(-30.0,125.0,75.0,0.0,false);
				Cameraoffset(60.0,20.0,25.0,20.0,false);
				Cinema_cut=Cinema_cut+1;
			endcase;
			
			case 1:
			endcase;
			
		endswitch;
	
endstate;

state lost;

		code

	if (cinemaskip) then
			cinema_cut = 20;
			resettimer (gti_Timer_21);
			killtimer(gti_timer_21);
			endmission(false,1);
			trans hang;
	endif;
 	
		switch cinema_cut
		
			case 0:	
				CinemaStart;
				setactivecamera (eca_cameraship);
				Targetfollowobject(playermech);
				Camerafollowobject(playermech);
				Targetoffset(0.0,4.0,0.0,0.0,false);
				Cameraoffset(0.0,20.0,75.0,0.0,false);
				Cameraoffset(60.0,125.0,25.0,20.0,false);
				Cinema_cut=Cinema_cut+1;
			endcase;
			
			case 1:
			endcase;
			
		endswitch;	
endstate;

This state is so no code is run after the user by-passes the end cinemas, the end mission code is already in effect.  The win and death state both trans to the hang state if the user ends the cinema prematurely.

state hang;
		
		code
		
endstate;

endfsm.
//******************************************************************

TIPS:
•	Lines can be REM’ed out with 2 slashed at the beginning of the line

•	Never underestimate how expensive it is to run AI loops.  Checking the distance of, or the status of something is much more expensive that a simple true / false test.  If you don’t always need to run an expensive test, wrap it in a Boolean.

	If ((shoudthisbetested) == true) then
		If (iswithin(ena_killbox,eva_bulldog2,500)) then
			Release_killers = true;
			Shouldthisbetested = false;
		Endif;
	Endif;

•	The host only runs multiplayer ABL scripts, and only certain information is sent to the clients.  A percentage of ABL functions will not work in Multiplayer.
•	Multiple lines can be commented out with:
/* 
“text”
*/